package game.Gamemode;

import game.Audio.AudioResources;
import game.Configuration;
import game.Helper;
import game.Map.Map;
import game.Map.Tile;
import game.Player.*;
import game.Scene;
import game.Sprites.DroppedBomb;
import game.Sprites.PlantedBomb;
import game.Sprites.SpriteResources;
import game.Vector3;
import game.Weapons.Bullet;
import game.Weapons.LoadWeapons;
import game.Weapons.Weapon_info;
import javafx.scene.paint.Color;

import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.awt.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

public class StandardManager extends GamemodeManager {
    public enum GamemodeState {
        FreezeTime,
        RoundPlaying,
        RoundFinished,
        GameFinished,
        BombPlanted
    }

    // CONFIGURATION VALUES
    final double freezeTime, buyTime, roundTime, roundFinishedTime;
    final int maxTeamRounds, maxWonRounds, nBots, startMoney;
    final String botNamePrefix;
    boolean friendlyFire;

    final int killMoney = 300, roundWinMoney = 1600, roundLostMoney = 800;
    final int maxMoney = 16000;

    // GAME VALUES
    int roundNumber; // Number of finished rounds
    int terroristWins;
    int counterWins;

    // GAME TEAMS
    Team terroristTeam;
    Team counterTeam;

    // Bomb
    PlantedBomb bombSprite;
    DroppedBomb droppedBomb = null;

    // Weapons
    public LoadWeapons loadWeapons_;
    protected StandardManager.GamemodeState gamemodeState;

    public HashMap<Weapon_info, Vector3> droppedWeapons;     // Weapons (and position) dropped on the ground

    // ROUND SPECIFIC VALUES
    protected double unfreezeTime; // Time when players must be unfrozen
    protected double shopCloseTime; // Time when the shop closes in the current round
    protected double roundBeginTime; // Sets the time where the next round begins
    protected double roundEndTime; // Time when the round ends

    // Sounds
    List<Clip> initRoundSounds, winRoundSounds;

    protected Vector3 bombPosition = null;

    public StandardManager(double freezeTime, double buyTime, double roundTime, double roundFinishedTime,
                           int maxTeamRounds, int maxWonRounds, boolean friendlyFire, int nBots, String botNamePrefix,
                           int startMoney, BotController.Difficulty botLevel, boolean playerInCounterTeam) throws Exception {
        super();

        if (freezeTime < 0 || buyTime < 0 || roundTime <= 0 || roundFinishedTime < 0) {
            throw new Exception();
        }

        if (maxTeamRounds <= 0 || maxWonRounds <= 0) {
            throw new Exception();
        }

        // Set up configuration values
        this.freezeTime = freezeTime;
        this.buyTime = buyTime;
        this.roundTime = roundTime;
        this.roundFinishedTime = roundFinishedTime;
        this.nBots = nBots;
        this.botNamePrefix = botNamePrefix;
        this.startMoney = startMoney;

        this.maxTeamRounds = maxTeamRounds;
        this.maxWonRounds = maxWonRounds;

        this.friendlyFire = friendlyFire;
        PlayerSkins.init();
        SpriteResources.init();
        loadIniRoundSound();

        loadWeapons_ = new LoadWeapons();
        terroristTeam = new Team("Terrorist", loadWeapons_.getTerroristWeapons(), PlayerSkins.getTerroristSkins(), Color.RED);
        counterTeam = new Team("Counter", loadWeapons_.getCounterWeapons(), PlayerSkins.getCounterSkins(), Color.BLUE);

        droppedWeapons = new HashMap<Weapon_info, Vector3>();

        Map map = Helper.mainScene.getMap();
        List<Vector3> tSpawn = map.gettSpawn();
        List<Vector3> ctSpawn = map.getctSpawn();
        Random rand = new Random(System.nanoTime()); //To pick random tile in spawn

        Vector3 randomElement = tSpawn.get(rand.nextInt(tSpawn.size()));
        Player pl;
        Team playerTeam, otherTeam;
        if (!playerInCounterTeam) {
            pl = new Player(Configuration.getNamePlayer(), terroristTeam, 0, map.localToGlobal(randomElement), randomElement.z, Color.WHITE);
            playerTeam = terroristTeam;
            otherTeam = counterTeam;
        } else {
            pl = new Player(Configuration.getNamePlayer(), counterTeam, 0, map.localToGlobal(randomElement), randomElement.z, Color.WHITE);
            playerTeam = counterTeam;
            otherTeam = terroristTeam;
        }
        Player.myPlayer = pl;
        PlayerController p = new PlayerController(pl);

        // Add  bots
        for (int i = 0; i < this.nBots; i++) {
            if (i < this.nBots - 1) {
                randomElement = tSpawn.get(rand.nextInt(tSpawn.size()));
                Player testBot1 = new Player(this.botNamePrefix + BotController.getBotName(), playerTeam, 0, map.localToGlobal(randomElement), randomElement.z, Color.BLUE);
                BotController b1 = new BotController(testBot1, 0, botLevel);
            }
            randomElement = ctSpawn.get(rand.nextInt(ctSpawn.size()));
            Player testBot2 = new Player(this.botNamePrefix + BotController.getBotName(), otherTeam, 0, map.localToGlobal(randomElement), randomElement.z, Color.RED);
            BotController b2 = new BotController(testBot2, 1, botLevel);
        }
        // Start Game
        this.gameStarted();
    }

    @Override
    public void addPlayer(Player player) {
        super.addPlayer(player);
        if (this.gamemodeState == StandardManager.GamemodeState.FreezeTime) {
            player.setFrozen(true);
            player.setHealth(100);
            player.setMoney(startMoney);
        } else {
            player.setFrozen(true);
            player.setHealth(0);
            player.setMoney(startMoney);
        }
    }

    @Override
    public void update(double deltaTime) {
        super.update(deltaTime);

        double currentTime = (double) System.currentTimeMillis() / 1000.0;

        // Still in frozen time
        if (this.gamemodeState == StandardManager.GamemodeState.FreezeTime) {
            // Unfreeze players after freeze time has passed
            if (currentTime >= this.unfreezeTime) {
                this.unfreezePlayers();
            }

            // Match has begun
        } else if (this.gamemodeState == StandardManager.GamemodeState.RoundPlaying) {
            // Finish round after round time has passed
            if (currentTime >= this.roundEndTime) {
                this.roundFinished();
            }
        }

        // Round has ended
        if (this.gamemodeState == StandardManager.GamemodeState.RoundFinished) {
            // Start round after round finish time has passed
            if (currentTime >= this.roundBeginTime) {
                this.roundStarted();
            }
        }
    }

    // GAME STATE RELATED FUNCITONS

    /**
     * Called when the game is started.
     */
    protected void gameStarted() {
        // Set up intial scores
        this.terroristWins = 0;
        this.counterWins = 0;
        this.bombSprite = null;

        // Set up initial player values (money, etc.)
        for (int i = 0; i < playerList.size(); i++) {
            playerList.get(i).setHealth(100);
            playerList.get(i).setMoney(startMoney);
        }

        System.out.println("TDM: Game begins! [" + terroristWins + "T / " + counterWins + "C]");

        //Show tutorial
        this.tutorialMessage();

        // Start first round
        this.roundStarted();
    }


    public void tutorialMessage(){
        if(Player.myPlayer != null){
            if(Player.myPlayer.getTeam() == terroristTeam){
                String teamName = Player.myPlayer.getTeam().getName();
                String tutorialText = "Welcome! You are now playing on the " + teamName + " team. "+
                        "Plant the bomb on one of the plant sites seen in the radar or kill all enemies to win a round. "
                        + "The first team to win " + maxWonRounds + " rounds wins the game. " +
                        "Good luck and have fun!";
                ChatMessage msg = new ChatMessage("Plant the bomb", Color.GREEN, tutorialText, true);
                this.addChatMessage(msg);
            }else{
                String teamName = Player.myPlayer.getTeam().getName();
                String tutorialText = "Welcome! You are now playing on the " + teamName + " team. "+
                        "Kill all terrorists or defuse the bomb to win a round. "
                        + "The first team to win " + maxWonRounds + " rounds wins the game. " +
                        "Good luck and have fun!";
                ChatMessage msg = new ChatMessage("Plant the bomb", Color.GREEN, tutorialText, true);
                this.addChatMessage(msg);

            }
        }
    }

    protected void gameFinished() {
        this.gamemodeState = StandardManager.GamemodeState.GameFinished;
        System.out.println("TDM: Game finished! [" + terroristWins + "T / " + counterWins + "C]");

        for (int i = 0; i < playerList.size(); i++) {
            playerList.get(i).setFrozen(true);
        }
    }


    public boolean isGameFinished() {
        return this.gamemodeState == StandardManager.GamemodeState.GameFinished;
    }

    public String getGameFinishedText() {
        if (counterWins > terroristWins) {
            return "Counter-Terrorist forces win!";
        } else if (counterWins < terroristWins) {
            return "Terrorists win!";
        } else {
            return "Draw...";
        }
    }

    public Color getGameFinishedColor() {
        if (counterWins > terroristWins) {
            return counterTeam.getTeamColor();
        } else if (counterWins > terroristWins) {
            return terroristTeam.getTeamColor();
        } else {
            return Color.GRAY;
        }
    }

    /**
     * Called when a game round is started.
     */
    protected void roundStarted() {

        // Delete planted bomb
        if(bombSprite != null){
            bombSprite.destroy();
        }
        bombSprite = null;
        bombPosition = null;
        // Delete dropped bomb
        if (this.droppedBomb != null) this.droppedBomb.destroy();
        this.droppedBomb = null;

        // Change team if max team rounds reached
        if (roundNumber == maxTeamRounds) {
            System.out.println("TDM: Switching teams!");

            // Swap team scores
            int oldCTWINS = counterWins;
            counterWins = terroristWins;
            terroristWins = oldCTWINS;

            //Swap player teams
            for (int i = 0; i < playerList.size(); i++) {
                Player pl = playerList.get(i);
                pl.getTeam().removePlayer(pl);
                if (pl.getTeam() == terroristTeam) {
                    counterTeam.addPlayer(pl);
                    pl.switchTeam(counterTeam);
                } else {
                    terroristTeam.addPlayer(pl);
                    pl.switchTeam(terroristTeam);
                }
                // Reset victim weapon
                pl.primary_weapon = null; //Remove primary weapon

                //Reset pistol
                pl.secondary_weapon = pl.getTeam().getInitialWeapon();
                pl.setPlayerArmor(0);
                pl.setMoney(startMoney);
            }
            Helper.myCamera.initStore(); //Reset store to update items available

            this.tutorialMessage();
        }
        System.out.println("TDM: Round " + (roundNumber + 1) + " started! [" + terroristWins + "T / " + counterWins + "C]");

        List<Vector3> tSpawn = Helper.mainScene.getMap().gettSpawn();
        List<Vector3> ctSpawn = Helper.mainScene.getMap().getctSpawn();
        Random rand = new Random(System.nanoTime()); //To pick random tile in spawn
        // Set up player values (health, ammo, position, etc.)
        for (int i = 0; i < playerList.size(); i++) {
            Player pl = playerList.get(i);

            // Give earned money
            if (roundNumber != maxTeamRounds) {
                pl.giveEarnedMoney();
                int money = pl.getMoney();
                if (money > maxMoney)
                    pl.setMoney(maxMoney);
            }

            // Set position and rotation
            Vector3 spawnLoc;
            if (pl.getTeam() == terroristTeam) {
                spawnLoc = tSpawn.get(rand.nextInt(tSpawn.size()));
            } else {
                spawnLoc = ctSpawn.get(rand.nextInt(ctSpawn.size()));
            }
            double spawnRot = spawnLoc.z;
            pl.setPosition(spawnLoc);
            pl.setZ(4.0);
            pl.setRotation(spawnRot);

            //Set default ammo
            pl.resetWeaponsAmmo();

            //Set player health to 100
            pl.setHealth(100);
            pl.setHasBomb(false);
        }

        // Set own player on front (viewable on top of other players)
        Player.myPlayer.setZ(8.0);

        //Give the bomb to random Terrorist player
        List<Player> terrorists = terroristTeam.getPlayers();
        terrorists.get(rand.nextInt(terrorists.size())).setHasBomb(true);
        this.bombPosition = null; //Remove bomb from the map

        // Set up freeze time
        if (this.freezeTime > 0) {
            this.freezePlayers();
        } else {
            this.unfreezeTime = (double) System.currentTimeMillis() / 1000.0;
            this.unfreezePlayers();
        }

        //Notify players of round start
        for (Player pl : playerList) {
            pl.getController().notifyRoundStart();
        }
        System.out.println("PLAYERS UNFROZEN AND NOTIFIED");
    }

    /**
     * Freezes players and sets the unfreeze time of the round.
     */
    protected void freezePlayers() {
        this.gamemodeState = StandardManager.GamemodeState.FreezeTime;
        System.out.println("TDM: Players frozen!");

        // Freeze players
        for (int i = 0; i < playerList.size(); i++) {
            playerList.get(i).setFrozen(true);
        }

        // Set unfreeze time
        this.unfreezeTime = (double) System.currentTimeMillis() / 1000.0 + this.freezeTime;

        // Set buy time
        this.shopCloseTime = (double) System.currentTimeMillis() / 1000.0 + this.buyTime;
    }

    /**
     * Called when the freeze time of a round ends.
     * Unfreezes players and sets the round end time.
     */
    protected void unfreezePlayers() {
        this.gamemodeState = StandardManager.GamemodeState.RoundPlaying;
        System.out.println("TDM: Players are free!");

        // Unfreeze players
        for (int i = 0; i < playerList.size(); i++) {
            playerList.get(i).setFrozen(false);
        }

        // Set round end time
        this.roundEndTime = (double) System.currentTimeMillis() / 1000.0 + this.roundTime;

        try {
            playInitRoundSound();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("DONE FREEING PLAYERS");

    }

    public void loadIniRoundSound() throws UnsupportedAudioFileException, IOException, LineUnavailableException {
        initRoundSounds = new ArrayList<>();
        String fileName = "audio/radio/go.ogg";
        initRoundSounds.add(AudioResources.loadClip(fileName));
        fileName = "audio/radio/letsgo.ogg";
        initRoundSounds.add(AudioResources.loadClip(fileName));
        fileName = "audio/radio/moveout.ogg";
        initRoundSounds.add(AudioResources.loadClip(fileName));

    }

    public void playInitRoundSound() throws LineUnavailableException, IOException, UnsupportedAudioFileException {
        int max = initRoundSounds.size();
        int rnd = (int) (Math.random() * max); // max not included

        Helper.myCamera.play3DSound(initRoundSounds.get(rnd), Helper.myCamera.getPosition());
    }

    public void playWinSound(int i) throws IOException, UnsupportedAudioFileException, LineUnavailableException {
        String fileName = "";
        if (i == 0) {
            fileName = "audio/radio/ctwin.ogg";
        } else {
            fileName = "audio/radio/terwin.ogg";
        }
        //TODO change
        Helper.myCamera.play3DSound(AudioResources.loadClip(fileName), Helper.myCamera.getPosition());
    }


    protected void giveTeamMoney(Team team, int quanity) {
        for (Player pl : team.getPlayers()) {
            pl.earnMoney(quanity);
        }
    }

    /**
     * Called when a game round is finished.
     */
    protected void roundFinished() {
        this.roundNumber += 1;
        System.out.println("TDM: Round finished!");
        // Add win to team with most people alive
        int terrorMinusCounter;
        if (isBombPlanted()) {
            terrorMinusCounter = 1; //Bomb exploded, terrorist win

            // Make bomb explode
            bombPosition = null;
        } else {
            if (aliveTerroristsCount() > 0 && aliveCounterCount() > 0)
                terrorMinusCounter = -1; //Bomb wasnt planted, ct win
            else terrorMinusCounter = aliveTerroristsCount() - aliveCounterCount(); //One team eliminated the other
        }
        this.gamemodeState = StandardManager.GamemodeState.RoundFinished;
        if (terrorMinusCounter > 0) {
            terroristWins++;
            try {
                playWinSound(1);
            } catch (Exception e) {
                System.out.println(e);
            }
            System.out.println("Terrorists win! [" + terroristWins + "T / " + counterWins + "C]");
            giveTeamMoney(terroristTeam, roundWinMoney);
            giveTeamMoney(counterTeam, roundLostMoney);

            if(Scene.getInstance().getCamera() != null){
                Scene.getInstance().getCamera().showRoundWinMessage("Terrorists Win");
            }
        } else if (terrorMinusCounter < 0) {
            counterWins++;
            try {
                playWinSound(0);
            } catch (Exception e) {
                System.out.println(e);
            }
            System.out.println("Counter win! [" + terroristWins + "T / " + counterWins + "C]");
            giveTeamMoney(counterTeam, roundWinMoney);
            giveTeamMoney(terroristTeam, roundLostMoney);

            if(Scene.getInstance().getCamera() != null){
                Scene.getInstance().getCamera().showRoundWinMessage("Counter-Terrorists Win");
            }
        } else {
            System.out.println("Draw round [" + terroristWins + "T / " + counterWins + "C]");
            giveTeamMoney(counterTeam, roundLostMoney);
            giveTeamMoney(terroristTeam, roundLostMoney);
        }


        // Game finished because of team reaching max score
        if (this.terroristWins >= maxWonRounds || this.counterWins >= maxWonRounds) {
            this.gameFinished();
        }
        // Game finished, reached max round count
        else if (this.roundNumber >= this.maxTeamRounds * 2) {
            this.gameFinished();
        }

        // Set the time for the next round to begin
        if (this.roundFinishedTime > 0) {
            this.roundBeginTime = (double) System.currentTimeMillis() / 1000.0 + this.roundFinishedTime;
        } else {
            this.roundBeginTime = (double) System.currentTimeMillis() / 1000.0;
            this.roundStarted();
        }
    }

    @Override
    public void playerSpawned(Player player) {

    }

    @Override
    public void playerChangedTile(Player player, Tile newTile, Tile oldTile) {

    }

    // DAMAGE RELATED FUNCTIONS
    @Override
    public boolean bulletHit(Bullet bullet, Damageable obj) {
        //TODO check if damageable object can be hit (check friendly fire)
        if (obj.getHealth() > 0 && (bullet.getPlayer().getTeam() != obj.getTeam() || friendlyFire)) {
            //Bullet can hit
            bullet.hit(obj);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean knifeHit(Player killer, Weapon_info wpn, Damageable obj) {
        //TODO check if damageable object can be hit (check friendly fire)
        if (obj.getHealth() > 0 && (killer.getTeam() != obj.getTeam() || friendlyFire)) {
            //TODO check knife damage
            obj.doDamage(killer, wpn, wpn.getDamage());
            return true;
        } else {
            return false;
        }
    }


    @Override
    public void playerKilled(Player victim, Player killer, Weapon_info weaponInfo) {
        super.playerKilled(victim, killer, weaponInfo);

        // Reset victim weapon
        victim.primary_weapon = null; //Remove primary weapon

        //Reset pistol
        victim.secondary_weapon = victim.getTeam().getInitialWeapon();

        // Check if there are no players on victims team to end round
        if (this.gamemodeState != GamemodeState.RoundFinished && this.gamemodeState != GamemodeState.GameFinished &&
                ((victim.getTeam() == terroristTeam && aliveTerroristsCount() == 0 && !isBombPlanted()) ||
                        victim.getTeam() == counterTeam && aliveCounterCount() == 0)) {
            this.roundFinished();
        }

        //TODO Add score (money) to killer player if team is different than own
        if (victim.getTeam() != killer.getTeam()) {
            killer.addKill(1);
            killer.earnMoney(killMoney);
        }
    }


    // AUXILIARY FUNCTIONS
    protected int getTeamAlivePlayerCount(boolean terrorists) {
        int aliveCount = 0;
        for (Player player : playerList) {
            if (player.isAlive()) {
                if (player.getTeam() == terroristTeam && terrorists || player.getTeam() == counterTeam && !terrorists) {
                    aliveCount++;
                }
            }
        }
        return aliveCount;
    }

    public int aliveTerroristsCount() {
        return getTeamAlivePlayerCount(true);
    }

    protected int aliveCounterCount() {
        return getTeamAlivePlayerCount(false);
    }

    public Team getTerroristTeam() {
        return this.terroristTeam;
    }

    public Team getCounterTeam() {
        return this.counterTeam;
    }

    @Override
    public double getRemainingTime() {
        if (this.gamemodeState == StandardManager.GamemodeState.RoundPlaying) {
            //System.out.println("CURRENTTIME: " + (int)(System.currentTimeMillis() / 1000.0) + " ROUNDENDTIME: " + this.roundEndTime);
            return (int) (this.roundEndTime - System.currentTimeMillis() / 1000.0 + 1.0);
        } else if (this.gamemodeState == StandardManager.GamemodeState.FreezeTime) {
            //System.out.println("CURRENTTIME: " + (int)(System.currentTimeMillis() / 1000.0) + " ROUNDENDTIME: " + this.roundEndTime);
            return (int) (this.unfreezeTime - System.currentTimeMillis() / 1000.0 + 1.0);
        } else if (this.gamemodeState == StandardManager.GamemodeState.RoundFinished) {
            //System.out.println("CURRENTTIME: " + (int)(System.currentTimeMillis() / 1000.0) + " ROUNDENDTIME: " + this.roundEndTime);
            return (int) (this.roundBeginTime - System.currentTimeMillis() / 1000.0 + 1.0);
        } else {
            return 0;
        }
    }

    @Override
    public LoadWeapons getWeapons() {
        return this.loadWeapons_;
    }

    @Override
    public double getShopCloseTime() {
        return this.shopCloseTime;
    }

    public int getTerroristWins() {
        return terroristWins;
    }

    public int getCounterWins() {
        return counterWins;
    }

    @Override
    public HashMap<Weapon_info, Vector3> getDroppedWeapons() {
        return droppedWeapons;
    }

    //TODO: Timer se sustituye por icono de bomba y tiempo restante para explosion (todo parpadea rojo y amarillo)
    public void plantBomb(Player p) {
        bombPosition = new Vector3(p.getX(), p.getY(), 0.0);
        try {
            this.bombSprite = new PlantedBomb(bombPosition, p.getRotation(), 30.0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //TODO: Si la bomba tarda X en explotar y el tiempo restante en la ronda es <X...
        //30 secs until bomb detonation
        this.roundEndTime = (double) System.currentTimeMillis() / 1000.0 + 30;
    }

    public boolean isBombPlanted() {
        return this.bombPosition != null && this.gamemodeState == GamemodeState.RoundPlaying;
    }

    public void defuseBomb() {
        if (this.gamemodeState != GamemodeState.RoundFinished) {
            bombPosition = null;
            if (bombSprite != null) {
                bombSprite.defuse();
            }
            bombSprite = null;

            //TODO: Algo mas que hacer?
            roundFinished();
        }
    }

    public Vector3 getBombPosition() {
        return this.bombPosition;
    }

    public void dropBomb(Player p) throws Exception {
        this.droppedBomb = new DroppedBomb(p.getPosition(), p.getRotation(), Color.WHITE);
        //Notify bots of bomb drop
        for(int i=0; i<playerList.size(); i++){
            Player pl = playerList.get(i);
            if(pl.isAlive() && pl.getController() instanceof BotController){
                ((BotController) pl.getController()).notifyBombDrop();
            }
        }
    }

    public void pickBomb() {
        this.droppedBomb = null;
    }

    public boolean isBombDropped() {
        return this.droppedBomb != null;
    }

    public Vector3 getDroppedBombPosition() {
        return this.droppedBomb.getPosition();
    }

}
